#ifdef USE_LIBUV
#include "uv.h"

#include "../core/Application.h"

#include "TcpSocket.h"


namespace LuaGui {
	static void on_tcp_socket_alloc_buffer_cb(uv_handle_t* handle, size_t suggested_size, uv_buf_t* buf)
	{
		buf->len = (unsigned long)suggested_size;
		buf->base = new char[suggested_size];
	}

	static void on_tcp_socket_read_cb(uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf)
	{
		TcpSocket* socket = (TcpSocket*)stream->data;
		if (socket == nullptr)
		{
			// socket not found
			return;
		}

		if (nread > 0)
		{
			// lua callback
			std::string data(buf->base, nread);
			CallLuaGlobalFunction("__on_net__", "OnNetRecv", socket->GetUID(), socket->GetServerUID(), data);
		}
		else if (nread == 0)
		{

		}
		else
		{
			// read error
			if (nread != UV_EOF)
			{
				GetApplication()->Warn("on_tcp_socket_read_cb read error: " + std::string(uv_err_name((int)nread)));
			}
			else
			{
				GetApplication()->Info("client disconnection");
			}
			socket->Close();
		}

		// free buf
		if (buf->base != nullptr)
		{
			delete[] buf->base;
		}
	}

	static void on_tcp_socket_write_cb(uv_write_t* req, int status)
	{
		if (status != 0)
		{
			GetApplication()->Error("on_tcp_socket_write_cb: " + std::string(uv_strerror(status)));
		}
		else
		{
			//CallLuaGlobalFunction("__on_net__", "OnNetSend");
		}
		delete[]((char*)(req->data));
		delete req;
	}

	static void on_tcp_socket_close_cb(uv_handle_t* handle)
	{
		TcpSocket* socket = (TcpSocket*)handle->data;
		if (socket == nullptr) return;
		CallLuaGlobalFunction("__on_net__", "OnNetDisconnection", socket->GetUID(), socket->GetServerUID());
		delete socket;
	}

	static void on_tcp_client_new_connection_cb(uv_connect_t* req, int status)
	{
		TcpSocket* socket = (TcpSocket*)req->handle->data;
		delete req;

		if (socket == nullptr) return;

		if (status < 0) { // error!
			GetApplication()->Error("on_tcp_client_new_connection_cb: " + std::string(uv_strerror(status)));
			//CallLuaGlobalFunction("__on_net__", "OnNetConnection", socket->GetUID(), false);
			delete socket;
			return;
		}

		socket->SetServerUID(0);

		CallLuaGlobalFunction("__on_net__", "OnNetConnection", socket->GetUID(), socket->GetServerUID());
		uv_read_start(socket->GetUVStream(), on_tcp_socket_alloc_buffer_cb, on_tcp_socket_read_cb);
	}


	std::unordered_map<uint64_t, TcpSocket*> TcpSocket::s_tcp_sockets;

	TcpSocket::TcpSocket()
	{
		m_uv_tcp = new uv_tcp_t;
		m_uv_tcp->data = this;
		m_server_uid = 0;
		uv_tcp_init(uv_default_loop(), GetUVTcp());
		s_tcp_sockets[GetUID()] = this;
	}

	TcpSocket::~TcpSocket()
	{
		delete m_uv_tcp;
		s_tcp_sockets.erase(GetUID());
	}

	TcpSocket* TcpSocket::GetTcpSocketByUID(uint64_t uid)
	{
		std::unordered_map<uint64_t, TcpSocket*>::iterator it = s_tcp_sockets.find(uid);
		if (it == s_tcp_sockets.end()) return nullptr;
		return it->second;
	}

	TcpSocket* TcpSocket::Connect(std::string ip, unsigned int port)
	{
		TcpSocket* socket = new TcpSocket;

		sockaddr_in addr;
		uv_ip4_addr(ip.c_str(), port, &addr);
		uv_connect_t* req = new uv_connect_t;
		int ret = uv_tcp_connect(req, socket->GetUVTcp(), (const struct sockaddr*)&addr, on_tcp_client_new_connection_cb);

		return socket;
	}

	int TcpSocket::Send(std::string buf)
	{
		//printf("-----send:%p %p\n", this, this->GetUVStream());
		if (uv_is_writable(GetUVStream()) == 0) return 0;
		uv_write_t* req = new uv_write_t;
		uint32_t size = (uint32_t)buf.length();
		char* base = new char[size];
		memcpy(base, buf.data(), size);
		uv_buf_t uv_buf = uv_buf_init(base, size);
		req->data = base;
		return uv_write(req, GetUVStream(), &uv_buf, 1, on_tcp_socket_write_cb);
	}

	void TcpSocket::OnRecv()
	{
		uv_read_start(GetUVStream(), on_tcp_socket_alloc_buffer_cb, on_tcp_socket_read_cb);
	}

	void TcpSocket::Close()
	{
		uv_close((uv_handle_t*)m_uv_tcp, on_tcp_socket_close_cb);
	}

	std::string TcpSocket::GetIp()
	{
		sockaddr_in addr;
		int len = sizeof(sockaddr_in);
		uv_tcp_getsockname(GetUVTcp(), (struct sockaddr*)&addr, &len);
		char buf[512] = "";
		uv_ip4_name(&addr, buf, sizeof(buf));
		return buf;
	}

	unsigned int TcpSocket::GetPort()
	{
		sockaddr_in addr;
		int len = sizeof(sockaddr_in);
		uv_tcp_getsockname(GetUVTcp(), (struct sockaddr*)&addr, &len);
		return ntohs(addr.sin_port);
	}

	std::string TcpSocket::GetPeerIp()
	{
		sockaddr_in addr;
		int len = sizeof(sockaddr_in);
		uv_tcp_getpeername(GetUVTcp(), (struct sockaddr*)&addr, &len);
		char buf[512] = "";
		uv_ip4_name(&addr, buf, sizeof(buf));
		return buf;
	}

	unsigned int TcpSocket::GetPeerPort()
	{
		sockaddr_in addr;
		int len = sizeof(sockaddr_in);
		uv_tcp_getpeername(GetUVTcp(), (struct sockaddr*)&addr, &len);
		return ntohs(addr.sin_port);
	}
}

#endif // USE_LIBUV

